/*
 * Copyright (c) 2010, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef ALKES_TMATRIX2_STACK_H_INCLUDED_
#define ALKES_TMATRIX2_STACK_H_INCLUDED_

#include "alkes/internal.h"
#include "alkes/core/Types.h"
#include "alkes/core/TExtendBuffer.h"

namespace alkes {

enum MatrixType
{
    AL_MT_PUSH = 0,
    AL_MT_MARKER,
    AL_MT_IDENTITY,
    AL_MT_2D_TRANSLATE,
    AL_MT_2D_ROTATE,
    AL_MT_2D_SCALE,
    AL_MT_2D_SKEWX,
    AL_MT_2D_SKEWY,
    AL_MT_3D_START,
};

template<typename T>
class TMatrix2Stack
{
public:

    TMatrix2Stack();
    ~TMatrix2Stack();

    void push();
    void pop();

    void translate2D(T x, T y);
    void rotate2D(T angle, T x, T y);
    void scale2D(T xscale, T yscale);
    void skewX(T angle);
    void skewY(T angle);

    void setMarker();

public:

    struct TranslateValue
    {
        T x, y;
    };

    struct RotateValue
    {
        T angle, x, y;
    };

    struct ScaleValue
    {
        T x, y;
    };

    struct SkewXValue
    {
        T angle;
    };

    struct SkewYValue
    {
        T angle;
    };

    uint32_t getTopPointer() const;
    uint32_t getNextPointer(uint32_t pointer) const;
    bool getData(uint32_t pointer, uint8_t* type, const void** data) const;

protected:

    void pushData(uint8_t matrix_type, const void* data, uint16_t size);

    template<class T>
    void pushData(uint8_t matrix_type, const T& data)
    {
        pushData(matrix_type, &data, sizeof(T));
    }

private:

    struct Header
    {
        uint8_t type;
        uint8_t reserve;
        uint16_t size;
    };

    bool getStackData(uint32_t pointer, uint8_t* type, void** data) const;

private:
    TExtendBuffer<CopyExtendPolicy<StdlibAllocator> > buffer_;
    uint32_t pointer_;
};


template<typename T>
TMatrix2Stack<T>::TMatrix2Stack()
: pointer_(0)
{
}

template<typename T>
TMatrix2Stack<T>::~TMatrix2Stack()
{
}

template<typename T>
void TMatrix2Stack<T>::push()
{
    //AL_TRACE();
    pushData(AL_MT_PUSH, 0, 0);
}

template<typename T>
void TMatrix2Stack<T>::pop()
{
    //AL_TRACE();
    uint32_t pointer = getTopPointer();
    uint8_t type;
    const void* data;
    while (getData(pointer, &type, &data))
    {
        pointer = getNextPointer(pointer);
        if (type == AL_MT_PUSH)
        {
            break;
        }
    }

    pointer_ = pointer;
}

template<typename T>
void TMatrix2Stack<T>::translate2D(T x, T y)
{
    uint8_t type;
    void* data;
    if (getStackData(pointer_, &type, &data)
        && type == AL_MT_2D_TRANSLATE)
    {
        //AL_TRACE();
        ((TranslateValue*)data)->x += x;
        ((TranslateValue*)data)->y += y;
    }
    else
    {
        TranslateValue value = {x, y};
        pushData(AL_MT_2D_TRANSLATE, value);
    }
}

template<typename T>
void TMatrix2Stack<T>::rotate2D(T angle, T x, T y)
{
    RotateValue value = {angle, x, y};
    pushData(AL_MT_2D_ROTATE, value);
}

template<typename T>
void TMatrix2Stack<T>::scale2D(T xscale, T yscale)
{
    ScaleValue value = {xscale, yscale};
    pushData(AL_MT_2D_SCALE, value);
}

template<typename T>
void TMatrix2Stack<T>::skewX(T angle)
{
    SkewXValue value = {angle};
    pushData(AL_MT_2D_SKEWX, value);
}

template<typename T>
void TMatrix2Stack<T>::skewY(T angle)
{
    SkewYValue value = {angle};
    pushData(AL_MT_2D_SKEWY, value);
}

template<typename T>
void TMatrix2Stack<T>::setMarker()
{
    pushData(AL_MT_MARKER, 0, 0);
}

template<typename T>
void TMatrix2Stack<T>::pushData(uint8_t matrix_type, const void* data, uint16_t size)
{
    uint8_t* ptr = (uint8_t*)buffer_.getBuffer(pointer_ + size + sizeof(Header));

    ptr += pointer_;
    if (data && size)
    {
        memcpy(ptr, data, size);
        ptr += size;
    }

    ((Header*)ptr)->type = (uint8_t)matrix_type;
    ((Header*)ptr)->size = size;

    pointer_ += size + sizeof(Header);
}

template<typename T>
uint32_t TMatrix2Stack<T>::getTopPointer() const
{
    return pointer_;
}

template<typename T>
uint32_t TMatrix2Stack<T>::getNextPointer(uint32_t pointer) const
{
    const uint8_t* p = (const uint8_t*)buffer_.pointer();
    if (!p || !pointer_ || !pointer || pointer_ < pointer)
        return 0;

    p += (pointer - sizeof(Header));

    return pointer - (sizeof(Header) + ((const Header*)p)->size);
}

template<typename T>
bool TMatrix2Stack<T>::getData(uint32_t pointer, uint8_t* type, const void** data) const
{
    uint8_t tmp_type;
    void* tmp_data;
    bool ret = getStackData(pointer, &tmp_type, &tmp_data);
    if (ret)
    {
        *type = tmp_type;
        *data = tmp_data;
    }
    return ret;
}

template<typename T>
bool TMatrix2Stack<T>::getStackData(uint32_t pointer, uint8_t* type, void** data) const
{
    uint8_t* p = (uint8_t*)buffer_.pointer();
    if (!p || !pointer_ || !pointer || pointer_ < pointer)
        return false;

    p += (pointer - sizeof(Header));

    *type = ((const Header*)p)->type;
    *data = p - ((const Header*)p)->size;

    return true;
}

}

#endif
